package com.usemodj.forum;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Date;
import java.util.UUID;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import com.usemodj.crypt.Crypt;
import com.usemodj.forum.service.MetaService;
public class PasswordHash {
private static Logger logger = Logger.getLogger( PasswordHash.class );
public static final int CRYPT_BLOWFISH = 1;
public static final int CRYPT_EXT_DES = 1;
String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
boolean portableHashes;
private String randomState;
private String rndValue;
private int iterationCountLog2;
public PasswordHash( int iterationCountLog2, boolean portableHashes) {
//this.itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
if( iterationCountLog2 < 4 || iterationCountLog2 > 31)
iterationCountLog2 = 8;
this.iterationCountLog2 = iterationCountLog2;
this.portableHashes = portableHashes;
//$this->random_state = microtime() . (function_exists('getmypid') ? getmypid() : '') . uniqid(rand(), TRUE);
this.randomState = String.valueOf(new Date().getTime()) + UUID.randomUUID();
}
// public PasswordHash() {
// // TODO Auto-generated constructor stub
// }
public static boolean checkPassword(String userPass, String hashPass,
long userId) {
// list($hash, $broken) = array_pad( explode( '---', $hash ), 2, '' );
PasswordHash hasher = new PasswordHash(8, true);
boolean check = hasher.checkPassword(userPass, hashPass);
return check;
}
private boolean checkPassword(String userPass, String hashPass) {
String hash = cryptPrivate( userPass, hashPass);
logger.debug("-- checkPassword cryptPrivate=" + hash);
if( "*".equals( hash.charAt(0))){
//hash = UnixCrypt.crypt(hashPass, userPass);
//hash = JCrypt.crypt(hashPass, userPass);
hash = Crypt.crypt(hashPass, userPass);
}
logger.debug("-- checkPassword Crypt=" + hash);
logger.debug("-- hashPass=" + hashPass);
return hash.equals( hashPass);
}
private String cryptPrivate(String userPass, String hashPass) {
String output = "*0";
if( output.equals( hashPass.substring(0, 2)))
output = "*1";
if( ! "$P$".equals( hashPass.substring(0, 3)))
return output;
int countLog2 = this.itoa64.indexOf( hashPass.charAt(3));
logger.debug("--- cryptPrivate() countLog2="+ countLog2);
if( countLog2 < 7 || countLog2 > 30)
return output;
int count = 1 << countLog2;
String salt = hashPass.substring( 4, 12);
logger.debug("-- salt: "+ salt);
if( salt.length() != 8)
return output;
byte[] hash = DigestUtils.md5( salt + userPass);
//String hash =DigestUtils.md5Hex(salt + userPass);
//logger.debug("-- md5Hex("+ salt +userPass+ "): "+ Hex.encodeHexString(hash));
//logger.debug("-- md5( salt +userPass):encode64: "+this.encode64( hash, 16));
do {
hash = DigestUtils.md5( StringUtils.newStringUtf8( hash) + userPass);
//hash = DigestUtils.md5( StringUtils.newStringUsAscii( hash) + userPass);
//hash = DigestUtils.md5Hex( hash + userPass);
//logger.debug("-- md5( hash +userPass): "+ Hex.encodeHexString(hash));
//logger.debug("-- md5( hash +userPass):encode64: "+this.encode64( hash, 16));
} while( --count > 0);
output = hashPass.substring( 0, 12);
output += this.encode64( hash, 16);
return output;
}
private String encode64(byte[] input, int count) {
String output ="";
int i = 0;
do {
int value = (int)input[i++]; //php ord($input[$i++])
output += this.itoa64.charAt( value & 0x3f);
if( i < count)
value |=((int)input[i]) << 8;
output += this.itoa64.charAt((value >> 6) & 0x3f);
if( i++ >= count)
break;
if( i < count)
value |= ((int)input[i]) << 16;
output += this.itoa64.charAt( (value >> 12) & 0x3f);
if( i++ >= count)
break;
output += this.itoa64.charAt( (value >> 18) & 0x3f);
} while( i < count);
return output;
}
public int rand(SqlSession sqlSession, int min, int max){
String seed = getTransient( sqlSession, "random_seed");
//Reset rndValue after 14 uses
// 32(md5) + 40(sha1) + 40(sha1)/8 = 14 random numbers from rndValue
if( null == rndValue || rndValue.length() < 8) {
rndValue = DigestUtils.md5Hex( UUID.randomUUID() + seed);
rndValue += DigestUtils.shaHex( rndValue);
rndValue += DigestUtils.shaHex( rndValue + seed);
seed = DigestUtils.md5Hex(seed + rndValue );
setTransient(sqlSession, "random_seed", seed, 0);
}
//Take the first 8 digits for our value
String value = rndValue.substring(0, 8);
// Strip the first eight, leaving the remainder for the next call to wp_rand()
rndValue = rndValue.substring( 8);
long nValue = Math.abs( Long.parseLong( value,16));
// Reduce the value to be within the min - max range
// 4294967295 = 0xffffffff = max random number
// Integer.MAX_VALUE= 2147483647
if( max != 0)
nValue = min +(long)((max - min +1) * ((double)nValue/(4294967295L+1)));
logger.debug("--long nValue: " + nValue);
return Math.abs( (int)nValue);
}
private void setTransient(SqlSession sqlSession, String transients, String seed, long expiration) {
transients = "bp_bbpress_"+ transients;
String transientTimeout = "_transient_timeout_" + transients;
transients = "_transient_" + transients;
MetaService ms = new MetaService();
if( 0 != expiration) {
long epoch = System.currentTimeMillis()/1000; //sec
try {
ms.updateMeta(sqlSession, 0, transientTimeout, String.valueOf(epoch + expiration), "bb_option");
} catch (Exception e) {
// e.printStackTrace();
logger.error("-- PasswordHash.setTransient() Exception: " + e.getMessage());
}
}
try {
ms.updateMeta(sqlSession, 0, transients, seed, "bb_option" );
} catch (Exception e) {
//e.printStackTrace();
logger.error("-- PasswordHash.setTransient() Exception: " + e.getMessage());
}
}
private String getTransient(SqlSession sqlSession, String trans) {
String trans2 = "bp_bbpress_"+ trans;
String transientOption = "_transient_" + trans2;
String transientTimeout = "_transient_timeout_" + trans2;
MetaService ms = new MetaService();
String value = null;
try {
String timeout = ms.getBBOption(sqlSession, transientTimeout);
logger.debug("-- transientTimeout: "+ timeout);
if( null != timeout){
try {
long lTimeout = Long.parseLong( timeout);
long epoch = System.currentTimeMillis()/1000; //sec
if( lTimeout < epoch) {
ms.deleteBBOption( sqlSession, transientOption);
ms.deleteBBOption(sqlSession, transientTimeout);
return null;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
value = ms.getBBOption(sqlSession, transientOption);
} catch (Exception e) {
e.printStackTrace();
logger.error("PasswordHash.getTransient() Exception: "+ e.getMessage());
}
return value;
}
public String generatePassword( SqlSession sqlSession, int length, boolean specialChars){
//default int length = 12, boolean specialChars = true
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
if( specialChars)
chars += "@#$%^&*()";
String password = "";
for( int i = 0; i < length; i++ ){
int rand = rand(sqlSession, 0, chars.length() -1);
password += chars.substring(rand, rand+1);
}
return password;
}
public String hashPassword(String userPass) {
String random = "";
if( CRYPT_BLOWFISH == 1 && !this.portableHashes){
random = this.getRandomBytes( 16);
String hash = Crypt.crypt( this.gensaltBlowfish( random), userPass);
if( hash.length() == 60)
return hash;
}
if(CRYPT_EXT_DES == 1 && !this.portableHashes){
if( random.length() < 3)
random = this.getRandomBytes( 3);
String hash = Crypt.crypt( this.gensaltExtended( random), userPass);
if( hash.length() == 20)
return hash;
}
if( random.length() < 6)
random = this.getRandomBytes( 6);
String hash = this.cryptPrivate(userPass, this.gensaltPrivate( random));
if( hash.length() == 34)
return hash;
/*# Returning '*' on error is safe here, but would _not_ be safe
# in a crypt(3)-like function used _both_ for generating new
# hashes and for validating passwords against existing hashes.
*/
return"*";
}
private String gensaltPrivate(String input) {
String output = "$P$";
output += this.itoa64.charAt(Math.min(this.iterationCountLog2+5, 30));
output += this.encode64(input.getBytes(), 6);
return output;
}
private String gensaltExtended(String input) {
int countLog2 = Math.min( this.iterationCountLog2 + 8, 24);
/*# This should be odd to not reveal weak DES keys, and the
# maximum valid value is (2**24 - 1) which is odd anyway. */
int count = ( 1 << countLog2) -1;
String output = "_";
output += this.itoa64.charAt( count & 0x3f);
output += this.itoa64.charAt( (count >> 6) & 0x3f);
output += this.itoa64.charAt( (count >> 12) & 0x3f);
output += this.itoa64.charAt( (count >> 18) & 0x3f);
output += this.encode64(input.getBytes(), 3);
return output;
}
private String gensaltBlowfish(String input) {
/*# This one needs to use a different order of characters and a
# different encoding scheme from the one in encode64() above.
# We care because the last character in our encoded string will
# only represent 2 bits. While two known implementations of
# bcrypt will happily accept and correct a salt string which
# has the 4 unused bits set to non-zero, we do not want to take
# chances and we also do not want to waste an additional byte
# of entropy. */
String itoa64 ="./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
String output = "$2a$";
//$output .= chr(ord('0') + $this->iteration_count_log2 / 10);
output += String.valueOf((char)((int)'0' + iterationCountLog2/10));
//$output .= chr(ord('0') + $this->iteration_count_log2 % 10);
output += String.valueOf((char)((int)'0' + iterationCountLog2%10));
output += '$';
int i = 0;
do {
int c1 = (int)input.charAt(i++);
output += itoa64.charAt(c1 >> 2);
c1 = (c1 & 0x03) << 4;
if( i >= 16){
output += itoa64.charAt(c1);
break;
}
int c2 = (int)input.charAt(i++);
c1 |= c2 >> 4;
output += itoa64.charAt(c1);
c1 = (c2 & 0x0f) << 2;
c2 = (int)input.charAt(i++);
c1 |= c2 >> 6;
output += itoa64.charAt(c1);
output += itoa64.charAt( c2 & 0x3f);
} while( true);
return output;
}
public String getRandomBytes( int count) {
String output = "";
BufferedReader reader = null;
char[] cbuf = new char[count];
int length =0;
try {
reader = new BufferedReader(new FileReader("/dev/urandom"));
length = reader.read(cbuf, 0, count);
output = String.valueOf( cbuf);
} catch (FileNotFoundException e) {
// e.printStackTrace();
} catch (IOException e) {
// e.printStackTrace();
} finally {
if(reader != null)
try {
reader.close();
} catch (IOException e) {}
}
if( length < count){
output = "";
for( int i = 0; i < count; i += 16){
this.randomState = DigestUtils.md5Hex( new Date().getTime() + this.randomState);
//$output .= pack('H*', md5($this->random_state));
output += DigestUtils.md5Hex( this.randomState);
}
output = output.substring(0, count);
}
return output;
}
}